﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using ITAP.ShprossesBuilder.Modelling.Components.Constraints;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using ITAP.ShprossesBuilder.Modelling.Measurement;
using ITAP.Core.Modelling;

namespace ITAP.ShprossesBuilder.Modelling.Components
{
    [Serializable]
    public class ModelContainer : IComponent
    {
        private bool uniqueIdsEnabled = true;

        public bool UniqueIdsEnabled
        {
            get { return uniqueIdsEnabled; }
            set { uniqueIdsEnabled = value; }
        }

        private List<int> acceptedUniqueIdsToConnect;

        public List<int> AcceptedUniqueIdsToConnect
        {
            get { return acceptedUniqueIdsToConnect; }
            set { acceptedUniqueIdsToConnect = value; }
        }

        private int uniqueId;

        public int UniqueId
        {
            get { return uniqueId; }
            set { uniqueId = value; }
        }

        public bool InaccessibleFromMaterials
        {
            get { return false; }
            set {  }
        }


        private string name;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        private int serialNumber;

        public int SerialNumber
        {
            get { return serialNumber; }
            set { serialNumber = value; }
        }       

        private List<IComponent> selectedComponentsStack;
        /// <summary>
        /// Стек выделенных компонентов. Первым в списке идёт компонент, который был выделен первым. Этот список автоматически заполняется 
        /// в операции <see cref="CommandSelectComponent"/>, вручную его заполнять не рекомендуется.
        /// </summary>
        public List<IComponent> SelectedComponentsStack
        {
            get { return selectedComponentsStack; }
            set { selectedComponentsStack = value; }
        }

        private ModelCellRepository modelCellRepository;

        public ModelCellRepository ModelCellRepository
        {
            get { return modelCellRepository; }
        }

        private bool autoConnectComponentsInTolerance = true;

        public bool AutoConnectComponentsInTolerance
        {
            get { return autoConnectComponentsInTolerance; }
            set { autoConnectComponentsInTolerance = value; }
        }

        private List<IComponent> componentsInTolerance;

        public List<IComponent> ComponentsInTolerance
        {
            get { return componentsInTolerance; }
        }

        private double verticalCellMargin = 200;
        /// <summary>
        /// Отступ в координатах модели между ячейками моделей по вертикали. Нужен для того, чтобы было пространство 
        /// для вывода размеров и для визуального отделения моделей друг от друга.
        /// </summary>
        public double VerticalModelCellMargin
        {
            get { return verticalCellMargin; }
            set { verticalCellMargin = value; }
        }

        private double horizontalCellMargin = 200;
        /// <summary>
        /// Отступ в координатах модели между ячейками моделей по горизонтали. Нужен для того, чтобы было пространство 
        /// для вывода размеров и для визуального отделения моделей друг от друга.
        /// </summary>
        public double HorizontalModelCellMargin
        {
            get { return horizontalCellMargin; }
            set { horizontalCellMargin = value; }
        }

        private SizeF modelCellsSize;

        public SizeF ModelCellsSize
        {
            get { return modelCellsSize; }
            set
            {
            }
        }

        public RectangleF BoundingBox
        {
            get
            {
                SizeF size = this.Size;
                return new RectangleF(0, 0, size.Width, size.Height);
            }
        }

        public SizeF Size
        {
            get
            {
                SizeF size = new SizeF(0, 0);
                size.Width = (float)(this.modelCellRepository.GetTotalCellsWidth() + (this.horizontalCellMargin * 2 * this.modelCellRepository.ColumnsCount));
                size.Height = (float)(this.modelCellRepository.GetTotalCellsHeight() + (this.horizontalCellMargin * 2 * this.modelCellRepository.RowsCount));
                return size;
            }
        }

        private IComponent activeCell = null;

        public ModelContainer()
        {
            this.componentsInTolerance = new List<IComponent>();
            this.modelCellRepository = new ModelCellRepository();
            this.selectedComponentsStack = new List<IComponent>();
        }

        public List<IComponent> ModelCells
        {
            get { return this.InnerComponents; }
        }

        public IComponent ActiveCell
        {
            get 
            { 
                return activeCell; 
            }
        }

        #region IComponent Members

        public List<ITAP.ShprossesBuilder.Modelling.Components.Constraints.IConstraint> Constraints
        {
            get { throw new NotImplementedException(); }
        }

        public List<IComponent> ConnectedComponents
        {
            get { throw new NotImplementedException(); }
        }

        public List<IComponent> InnerComponents
        {
            get 
            {
                List<IComponent> innerComponents = new List<IComponent>();
                for (int i = 0; i < this.modelCellRepository.RowsCount; i++)
                {
                    for(int j = 0; j < this.modelCellRepository.ColumnsCount; j++)
                        if(this.modelCellRepository.Cells[i][j] != null)
                            innerComponents.Add(this.modelCellRepository.Cells[i][j]);
                }
                return innerComponents;
            }
        }

        public List<IComponent> AllInnerComponents
        {
            get
            {
                List<IComponent> components = new List<IComponent>();
                foreach (IComponent innerComponent in this.InnerComponents)
                    components.AddRange(innerComponent.AllInnerComponents);

                components.AddRange(this.InnerComponents);
                return components;
            }
        }

        public double Tolerance
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public bool IsChangedSinceLastUpdate
        {
            get { throw new NotImplementedException(); }
        }

        public List<ITAP.Core.Modelling.Primitives.IPrimitive> Primitives
        {
            get { throw new NotImplementedException(); }
        }

        public IComponent ConnectTo(IComponent component)
        {
            throw new NotImplementedException();
        }

        public IComponent DisconnectFrom(IComponent component)
        {
            throw new NotImplementedException();
        }

        public bool CanConnect(IComponent component)
        {
            throw new NotImplementedException();
        }

        public void Update()
        {
            foreach (ModelCell cell in this.InnerComponents)
                cell.Update();
        }

        public void AcceptVisitor(IModelVisitor visitor)
        {
            visitor.Visit(this);
        }

        public bool IsInTolerance(ModelPoint point)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Получение компонент, которые расположены под указанной точкой с указанным толерансом.
        /// </summary>
        /// <param name="point">Точка.</param>
        /// <param name="tolerance">Толеранс.</param>
        /// <returns>Список компонент, попавших в зону толеранса указанной точки. Список никак не отсортирован.</returns>
        public List<IComponent> GetComponentsByPoint(ModelPoint point, double tolerance)
        {
            List<IComponent> components = new List<IComponent>();
            foreach (IComponent innerComponent in this.AllInnerComponents)
                if (innerComponent.IsInTolerance(point, tolerance))
                    components.Add(innerComponent);

            if (components.Count == 0)
                components.Add(this);
            return components;
        }



        public bool MoveTo(ModelPoint destination)
        {
            throw new NotImplementedException();
        }

        public bool MoveWithOffset(ModelVector offset)
        {
            throw new NotImplementedException();
        }

        public bool Rotate(double angleOffset, ModelPoint rotateCenter)
        {
            throw new NotImplementedException();
        }

        public ModelPoint Position
        {
            get { throw new NotImplementedException(); }
        }

        public ITAP.Core.Modelling.Primitives.EndType GetComponentNearestEndType(IComponent component)
        {
            throw new NotImplementedException();
        }

        public List<Type> PreUpdatePriority
        {
            get { throw new NotImplementedException(); }
        }

        public List<Type> PostUpdatePriority
        {
            get { throw new NotImplementedException(); }
        }

        public IComponent Parent
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public bool EnableConstraintsFixing
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }


        public bool EnableAllConstraintsFixing
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public List<Type> CriticalConstraintTypes
        {
            get { throw new NotImplementedException(); }
        }

        public ConnectionInfo AcceptedComponentTypesToConnect
        {
            get { throw new NotImplementedException(); }
        }

        public string Article
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public IComponent ConnectToEnded(IEndedComponent endedComponent, ITAP.Core.Modelling.Primitives.EndType endToConnect)
        {
            throw new NotImplementedException();
        }

        [field: NonSerialized]
        public event ITAP.ShprossesBuilder.Modelling.Components.Constraints.ComponentConstraintEventDelegate BeforeFixingConstraint;

        [field: NonSerialized]
        public event ITAP.ShprossesBuilder.Modelling.Components.Constraints.ComponentConstraintEventDelegate AfterFixingConstraint;

        [field: NonSerialized]
        public event FatalModelErrorDelegate FatalModelError;

        public bool IsConnected(IComponent other)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Инициализация контейнера моделей. Передаётся хранилище моделей, которое хранит модели в двумерном списке для возможности 
        /// задания их положения относительно горизонтальной и вертикальной оси.
        /// </summary>
        /// <param name="cellRepository">Хранилище моделей.</param>
        public void Initialize(ModelCellRepository cellRepository)
        {
            this.modelCellRepository = cellRepository;
            this.RecalculateCells();           
            ModelCell firstCell = null;
            for (int i = 0; i < this.modelCellRepository.RowsCount; i++)
            {
                for (int j = 0; j < this.modelCellRepository.ColumnsCount; j++)
                {
                    ModelCell cell = this.modelCellRepository.Cells[i][j];
                    if (cell == null)
                        continue;
                    if (firstCell == null)
                        firstCell = cell;

                    cell.Parent = this;
                    
                    cell.BeforeFixingConstraint -= this.BeforeFixingInnerComponentConstraintEvent;
                    cell.BeforeFixingConstraint += new ComponentConstraintEventDelegate(this.BeforeFixingInnerComponentConstraintEvent);
                    cell.AfterFixingConstraint -= this.AfterFixingConstraint;
                    cell.AfterFixingConstraint += new ComponentConstraintEventDelegate(this.AfterFixingInnerComponentConstraintEvent);
                    cell.FatalModelError -= this.FatalModelErrorEvent;
                    cell.FatalModelError += new FatalModelErrorDelegate(this.FatalModelErrorEvent);                   
                }
            }
            if (firstCell != null)
            {
                firstCell.Selected = true;
                this.activeCell = firstCell;
            }
        }

        /// <summary>
        /// Перерасчёт положения моделей в зависимости от горизонтального и вертикального индексов модели в хранилище моделей.
        /// </summary>
        public void RecalculateCells()
        {
            this.modelCellRepository.RecalculateCellsSizes();
            this.modelCellRepository.RecalculateModelPositions();

            double offsetX = 0;
            double offsetY = this.horizontalCellMargin;

            for (int rowIter = 0; rowIter < this.modelCellRepository.RowsCount; rowIter++)
            {
                offsetX = this.verticalCellMargin;

                for(int columnIter = 0; columnIter < this.modelCellRepository.ColumnsCount; columnIter++)
                {
                    ModelCell cell = this.modelCellRepository.Cells[rowIter][columnIter];
                    if (cell == null)
                    {
                        offsetX += 2 * this.horizontalCellMargin + this.modelCellRepository.GetMaxWidth(columnIter);
                        continue;
                    }
                    cell.MoveToZeroPoint();
                    cell.MoveWithOffset(new ModelVector(offsetX, offsetY));
                    offsetX += 2 * this.horizontalCellMargin + this.modelCellRepository.GetMaxWidth(columnIter);
                }
                offsetY += 2 * this.verticalCellMargin + this.modelCellRepository.GetMaxHeight(rowIter);
            }
        }

        public void AddInnerComponent(IComponent component)
        {
            throw new Exception("Для добавления моделей в контейнер необходимо пользоваться методом Initialize()");
        }

        public void RemoveInnerComponent(IComponent component)
        {
        }

        public void BeforeFixingInnerComponentConstraintEvent(IComponent sender, ITAP.ShprossesBuilder.Modelling.Components.Constraints.ComponentConstraintEventArgs e)
        {
            if (this.BeforeFixingConstraint != null)
            {
                this.BeforeFixingConstraint.Invoke(sender, e);
            }

        }

        public void AfterFixingInnerComponentConstraintEvent(IComponent sender, ITAP.ShprossesBuilder.Modelling.Components.Constraints.ComponentConstraintEventArgs e)
        {
            if (this.AfterFixingConstraint != null)
            {
                this.AfterFixingConstraint.Invoke(sender, e);
            }
        }

        public void FatalModelErrorEvent(IComponent sender, FatalErrorEventArgs e)
        {
            if (this.FatalModelError != null)
                this.FatalModelError.Invoke(sender, e);
        }

        public ModelPoint GetNearestPointOnComponent(ModelPoint point, StickConnectionType stickType, out StickConnectionType sticked, ProjectionType projectionType)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region ICloneable Members

        public object Clone()
        {
            Stream stream = new MemoryStream();
            BinaryFormatter binary = new BinaryFormatter();
            binary.Serialize(stream, this);
            stream.Seek(0, SeekOrigin.Begin);
            object clone = binary.Deserialize(stream);
            stream.Close();
            return clone;
        }

        #endregion

        /// <summary>
        /// Выделение ячейки с моделью по точке. 
        /// </summary>
        /// <param name="point">Точка.</param>
        public void SelectActiveModelCellByPoint(ModelPoint point)
        {
            activeCell = null;
            foreach (ModelCell cell in this.InnerComponents)
            {
                cell.Selected = false;
                if (cell.Model.BoundingBox.Contains(new PointF((float)point.X, (float)point.Y)))
                {
                    activeCell = cell;
                    activeCell.Selected = true;     
                }
            }
        }

        #region IComponent Members

        private bool selected = false;
        public bool Selected
        {
            get
            {
                return selected;
            }
            set
            {
                selected = value;
            }
        }

        #endregion

        #region IComponent Members


        public bool IsPointInComponent(ModelPoint point)
        {
            return true;
        }

        #endregion

        #region IModelVisitor Members

        public void Visit(Model model)
        {
            throw new NotImplementedException();
        }

        public void Visit(Connector connector)
        {
            throw new NotImplementedException();
        }

        public void Visit(Shpross shpross)
        {
            throw new NotImplementedException();
        }

        public void Visit(Spacer spacer)
        {
            throw new NotImplementedException();
        }

        public void Visit(ShprossConnectorStub stub)
        {
            throw new NotImplementedException();
        }

        public void Visit(ShprossConnector shprossConnector)
        {
            throw new NotImplementedException();
        }

        public void Visit(SpacerElement spacerElement)
        {
            throw new NotImplementedException();
        }

        public void Visit(SpacerConnector spacerConnector)
        {
            throw new NotImplementedException();
        }

        public void Visit(ModelCell modelCell)
        {
            throw new NotImplementedException();
        }

        public void Visit(ModelContainer modelContainer)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IComponent Members

        public void DisconnectComponentsInTolerance()
        {
            throw new NotImplementedException();
        }

        #endregion

        public void DisconnectFromAll()
        {
            List<IComponent> componentsToDisconnect = new List<IComponent>();
            foreach (IComponent connectedComponent in this.ConnectedComponents)
            {
                componentsToDisconnect.Add(connectedComponent);
            }
            foreach (IComponent connectedComponent in componentsToDisconnect)
            {
                ConnectionProvider.Disconnect(this, connectedComponent);
            }
        }

        #region IComponent Members


        public ModelPoint GetNearestPointOnComponent(ModelPoint point, ModelPoint measurePoint, StickConnectionType stickType, out StickConnectionType sticked, ProjectionType projectionType)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IModelVisitor Members


        public void Visit(StickConnector stickConnector)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IComponent Members


        public void MoveToZeroPoint()
        {
            throw new NotImplementedException();
        }

        public void MoveToZeroXPoint()
        {
            throw new NotImplementedException();
        }

        public void MoveToZeroYPoint()
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IModelVisitor Members


        public void Visit(SnapOnCap snapOnCap)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IComponent Members


        public bool SelectByPoint(ModelPoint point)
        {
            this.SelectActiveModelCellByPoint(point);
            if (activeCell != null)
                return true;
            else
            {
                this.selected = true;
                return false;
            }
        }

        #endregion

        #region IComponent Members


        public void Rotate(double angleOffset)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IComponent Members


        public ModelPoint GetNearestPointOnComponent(ModelPoint point, StickConnectionType stickType, out StickConnectionType sticked, out IComponent innerComponent, ProjectionType projectionType)
        {
            throw new NotImplementedException();
        }

        public ModelPoint GetNearestPointOnComponent(ModelPoint point, ModelPoint measurePoint, StickConnectionType stickType, out StickConnectionType sticked, out IComponent innerComponent, ProjectionType projectionType)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IComponent Members


        public bool MoveTo(ModelPoint destination, RectangleF boundingRectangle)
        {
            throw new NotImplementedException();
        }

        public bool MoveWithOffset(ModelVector offset, RectangleF boundingRectangle)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IComponent Members


        public void RemoveAllInnerComponents()
        {
            foreach (IComponent innerComponent in this.InnerComponents)
                this.RemoveInnerComponent(innerComponent);
        }

        #endregion

        #region IComponent Members


        public bool Rotate(double angleOffset, ModelPoint rotateCenter, RectangleF boundingRectangle)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IComponent Members


        public bool Rotate(double angleOffset, RectangleF boundingRectangle)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IComponent Members


        public bool IsInTolerance(ModelPoint point, double tolerance)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IModelVisitor Members


        public void Visit(ShprossEndToEndConnector endToEndConnector)
        {
           // throw new NotImplementedException();
        }

        #endregion

        #region IModelVisitor Members


        public void Visit(ShprossChain shprossChain)
        {
    //        throw new NotImplementedException();
        }

        #endregion

        #region IComponent Members


        public IComponent Copy()
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IModelVisitor Members


        public void Visit(SpacerContainer spacerContainer)
        {
           // throw new NotImplementedException();
        }

        #endregion

        #region IModelVisitor Members


        public void Visit(CrossConnector crossConnector)
        {
    //        throw new NotImplementedException();
        }

        #endregion
    }
}
